package com.cqw.auth.config;

import com.cqw.auth.utils.JacksonUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description: StringRedisTemplate辅助操作类
 * @author cqw
 * @date 2020年08月14日 10:00:43
 */
@Component
@RequiredArgsConstructor(onConstructor=@__({@Autowired}))
public class RedisClient {

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 自增
     * @param key key
     * @return {@link Long}
     */
    public Long increment(String key) {
        return stringRedisTemplate.opsForValue().increment(key);
    }

    /**
     * 插入字符串
     * @param key 键
     * @param value 值
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 插入一个对象
     * @param key 键
     * @param obj 对象
     */
    public void set(String key, Object obj) {
        stringRedisTemplate.opsForValue().set(key, JacksonUtils.parseStr(obj));
    }

    /**
     * 插入数据并设置过期时间
     * @param key 键
     * @param value 值
     * @param time 时间长度
     * @param timeUnit 时间单位
     */
    public void setEx(String key, String value, Long time, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, value, time, timeUnit);
    }

    /**
     * 插入数据并设置过期时间
     * @param key 键
     * @param obj 缓存对象
     * @param time 时间长度
     * @param timeUnit 时间单位
     */
    public void setEx(String key, Object obj, Long time, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, JacksonUtils.parseStr(obj), time, timeUnit);
    }

    /**
     * 如果缓存不存在，插入数据
     * @param key 键
     * @param value 值
     * @param time 时间长度
     * @param timeUnit 时间单位
     * @return true：插入成功，false：键已存在,插入失败
     */
    public boolean setIfAbsent(String key, String value, Long time, TimeUnit timeUnit) {
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
        return absent != null && absent;
    }

    /**
     * 获取缓存数据
     * @param key 键
     * @return Null if not exist
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 直接返回对象
     * @param key 键
     * @param clazz 对象
     * @param <T> 对象
     * @return Null if not exist
     */
    public <T> T get(String key, Class<T> clazz) {
        String result = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(result)) {
            return null;
        }
        return JacksonUtils.parseObj(result, clazz);
    }

    /**
     * 删除缓存
     * @param key 删除缓存的键
     * @return true：插入成功，false：插入失败或键不存在
     */
    public boolean delete(String key) {
        if (!exist(key)) {
            return false;
        }
        Boolean delete = stringRedisTemplate.delete(key);
        return delete != null && delete;
    }

    /**
     * 删除缓存
     * @param keys 删除缓存的键
     * @return true：插入成功，false：插入失败或键不存在
     */
    public boolean delete(Collection<String> keys) {
        Long delete = stringRedisTemplate.delete(keys);
        return delete != null && delete == keys.size();
    }

    /**
     * 判断缓存是否存在
     * @param key 待检查key
     * @return true：存在，false：不存在
     */
    public boolean exist(String key) {
        Boolean exist = stringRedisTemplate.hasKey(key);
        return exist != null && exist;
    }

    /**
     * 添加一条hash格式数据
     * @param key 键
     * @param field 域
     * @param value 值
     */
    public void hSet(String key, String field, String value) {
        stringRedisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 添加一条hash格式数据
     * @param key 键
     * @param field 域
     * @param obj 值 -- 对象
     */
    public void hSet(String key, String field, Object obj) {
        stringRedisTemplate.opsForHash().put(key, field, JacksonUtils.parseStr(obj));
    }

    /**
     * 添加hash集合数据
     * @param key 键
     * @param map map集合
     */
    public void hSet(String key, Map<String, String> map) {
        stringRedisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 添加hash对象集合
     * @param key 键
     * @param objMap map对象集合
     */
    public void hSetObj(String key, Map<String, Object> objMap) {
        Map<String, String> map = new HashMap<>(objMap.size());
        for (Map.Entry<String, Object> entry : objMap.entrySet()) {
            map.put(entry.getKey(), JacksonUtils.parseStr(entry.getValue()));
        }
        stringRedisTemplate.opsForHash().putAll(key, map);
    }

    /**'
     * 根据键-域获取hash数据-字符串
     * @param key 键
     * @param field 域
     * @return Null if not exist
     */
    public String hGet(String key, String field) {
        Object obj = stringRedisTemplate.opsForHash().get(key, field);
        if (obj == null) {
            return null;
        }
        return String.valueOf(obj);
    }

    /**
     * 根据键-域获取hash数据-对象
     * @param key 键
     * @param field 域
     * @param clazz 对象
     * @param <T> 对象
     * @return Null if not exist
     */
    public <T> T hGet(String key, String field, Class<T> clazz) {
        Object obj = stringRedisTemplate.opsForHash().get(key, field);
        if (obj == null) {
            return null;
        }
        String json = String.valueOf(obj);
        return JacksonUtils.parseObj(json, clazz);
    }

    /**
     * 根据key获取hash集合
     * @param key 键
     * @return 字符串map, 不存在返回Collections.emptyMap()
     */
    public Map<String, String> hGet(String key) {
        HashOperations<String, String, String> operations = stringRedisTemplate.opsForHash();
        return operations.entries(key);
    }

    /**
     * 根据key查询hash对象集合
     * @param key 键
     * @param clazz 对象
     * @param <T> 对象
     * @return Collections.emptyMap() if not exist
     */
    public <T> Map<String, T> hGet(String key, Class<T> clazz) {
        Map<String, String> map = hGet(key);
        if (map == null || map.size() == 0) {
            return Collections.emptyMap();
        }
        Map<String, T> result = new HashMap<>(map.size());
        for (Map.Entry<String, String> entry : map.entrySet()) {
            result.put(entry.getKey(), JacksonUtils.parseObj(entry.getValue(), clazz));
        }
        return result;
    }

    /**
     * 插入字符串list
     * @param key 键
     * @param list 集合
     */
    public void push(String key, List<String> list) {
        stringRedisTemplate.opsForList().leftPushAll(key, list);
    }

    /**
     * 插入对象list
     * @param key 键
     * @param list 集合
     */
    public <T> void pushObj(String key, List<T> list) {
        List<String> strList = new ArrayList<>(list.size());
        for (T t : list) {
            strList.add(JacksonUtils.parseStr(t));
        }
        stringRedisTemplate.opsForList().leftPushAll(key, strList);
    }

    /**
     * 获取list
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @return Collections.emptyList(); if not exist
     */
    public List<String> range(String key, long start, long end) {
        return stringRedisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 遍历list
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @param clazz 对象类型
     * @param <T> 对象
     * @return Collections.emptyList(); if not exist
     */
    public <T> List<T> range(String key, long start, long end, Class<T> clazz) {
        List<String> list = stringRedisTemplate.opsForList().range(key, start, end);
        if (list == null) {
            return Collections.emptyList();
        }
        List<T> clazzList = new ArrayList<>(list.size());
        for (String str : list) {
            clazzList.add(JacksonUtils.parseObj(str, clazz));
        }
        return clazzList;
    }

    /**
     * 扫描所有以key开始的键
     * @param key 模糊匹配的key
     * @return {@link Set}
     */
    public Set<String> scan(String key) {
        Set<String> nameKeys = new HashSet<>();
        stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key + "*").count(1000).build());
            while (cursor.hasNext()) {
                nameKeys.add(new String(cursor.next()));
            }
            return nameKeys;
        });
        return nameKeys;
    }
    
}
